package gogm

import (
	"errors"
	"unsafe"
)

/*
#include <openssl/pem.h>
*/
import "C"

var (
	ErrParam      = errors.New("param error")
	ErrSign       = errors.New("sign error")
	ErrVerify     = errors.New("verify error")
	ErrGenR       = errors.New("generate R error")
	ErrComputeKey = errors.New("compute key error")
)

var default_sm2_id = []byte("1234567812345678")

func SM2Sign(key *EC_KEY, msg []byte) ([]byte, error) {
	cmsg := unsafe.Pointer(&msg[0])

	pkey := C.EVP_PKEY_new()
	defer C.EVP_PKEY_free(pkey)

	C.EVP_PKEY_set1_EC_KEY(pkey, key.eckey)

	var ctx C.EVP_MD_CTX
	defer C.EVP_MD_CTX_cleanup(&ctx)

	C.EVP_MD_CTX_init(&ctx)
	C.EVP_DigestInit(&ctx, C.EVP_sm3())
	C.EVP_DigestUpdate_ex(&ctx, cmsg, C.ulong(len(msg)), pkey)

	var sigl int = 72
	sig := make([]byte, sigl)

	csig := (*C.uchar)(unsafe.Pointer(&sig[0]))
	csigl := (*C.uint)(unsafe.Pointer(&sigl))
	if 1 != C.EVP_SignFinal(&ctx, csig, csigl, pkey) {
		return nil, ErrSign
	}

	return sig[:sigl], nil
}

func SM2Verify(key *EC_KEY, msg, sig []byte) error {
	cmsg := unsafe.Pointer(&msg[0])
	csig := (*C.uchar)(unsafe.Pointer(&sig[0]))

	pkey := C.EVP_PKEY_new()
	defer C.EVP_PKEY_free(pkey)

	C.EVP_PKEY_set1_EC_KEY(pkey, key.eckey)

	var ctx C.EVP_MD_CTX
	defer C.EVP_MD_CTX_cleanup(&ctx)

	C.EVP_MD_CTX_init(&ctx)
	C.EVP_DigestInit(&ctx, C.EVP_sm3())
	C.EVP_DigestUpdate_ex(&ctx, cmsg, C.ulong(len(msg)), pkey)
	if 1 != C.EVP_VerifyFinal(&ctx, csig, C.uint(len(sig)), pkey) {
		return ErrVerify
	}

	return nil
}

func SM2VerifyEx(key *PublicKey, msg, sig []byte) error {
	cmsg := unsafe.Pointer(&msg[0])
	csig := (*C.uchar)(unsafe.Pointer(&sig[0]))

	var ctx C.EVP_MD_CTX
	defer C.EVP_MD_CTX_cleanup(&ctx)

	C.EVP_MD_CTX_init(&ctx)
	C.EVP_DigestInit(&ctx, C.EVP_sm3())
	C.EVP_DigestUpdate_ex(&ctx, cmsg, C.ulong(len(msg)), key.pkey)
	if 1 != C.EVP_VerifyFinal(&ctx, csig, C.uint(len(sig)), key.pkey) {
		return ErrVerify
	}

	return nil
}

func SM2DHGenerateR(key *EC_KEY, id []byte) (Z, R, r []byte, err error) {
	if len(id) < 1 {
		err = ErrParam
		return
	}

	c_id := (*C.uchar)(unsafe.Pointer(&id[0]))

	Z = make([]byte, 32)
	R = make([]byte, 64)
	r = make([]byte, 32)
	c_Z := (*C.uchar)(unsafe.Pointer(&Z[0]))
	c_R := (*C.uchar)(unsafe.Pointer(&R[0]))
	c_r := (*C.uchar)(unsafe.Pointer(&r[0]))

	ret := C.SM2DH_generate_r(key.eckey, c_id, C.int(len(id)), c_Z, c_R, c_r)
	if ret == 0 {
		err = ErrGenR
		return
	}

	return
}

func SM2DHComputeKey(
	eckey_peer, eckey_local *EC_KEY,
	R_peer, R_local, id_peer,
	Z_local, r_local []byte,
	isinitiator uint8) (key []byte, err error) {

	c_R_peer := (*C.uchar)(unsafe.Pointer(&R_peer[0]))
	c_R_local := (*C.uchar)(unsafe.Pointer(&R_local[0]))
	c_id_peer := (*C.uchar)(unsafe.Pointer(&id_peer[0]))
	c_Z_local := (*C.uchar)(unsafe.Pointer(&Z_local[0]))
	c_r_local := (*C.uchar)(unsafe.Pointer(&r_local[0]))
	c_isinitiator := (C.uchar)(isinitiator)

	key = make([]byte, 16)
	c_key := (*C.uchar)(unsafe.Pointer(&key[0]))

	ret := C.SM2DH_compute_key(
		eckey_peer.eckey, eckey_local.eckey,
		c_R_peer, c_R_local,
		c_id_peer, C.ulong(len(id_peer)),
		c_Z_local, c_r_local, c_key, c_isinitiator)

	if ret == 0 {
		err = ErrComputeKey
		return
	}

	return
}
